﻿/******************************************************
* author :  cwj
* email  :  chenwenji_360@live.com 
* history:  created by cwj 2015/7/28 16:30:34 
* clrversion :4.0.30319.18444
******************************************************/

using Soul.DataAccess.Common.ORM;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Soul.DataAccess.Linq
{
    public class OneToManyMapper
    {
        private Dictionary<string, Item> tableCache = new Dictionary<string, Item>();
        private int tableNum;
        StringBuilder build = new StringBuilder();
        int bigTypeNum;
        Type bigType;
        Type collectionType;
        Tuple<string, DbForeignKeyInfo[]> outterFK;


        public string GetSql(Type bigType,Type collectionType)
        {
            this.bigType = bigType;
            this.collectionType = collectionType;
            GetFK(bigType);
            GetFK(collectionType);

            outterFK = Config.Instance.GetDbSchemInfo().GetOuterFKey(bigType.GetTableName(), collectionType.GetTableName());

            build.Append("Select ");

            BindCollectionSelect();

            build.Append(" From ");

            build.Append(string.Format(" {0} as t{1} ", tableCache[bigType.GetTableName()].Schem.TableName, tableCache[bigType.GetTableName()].TableNum));

            BindCollectionJoin();

            return build.ToString();
        }

        private void BindCollectionJoin()
        {
            var outterTableNum = tableCache.ContainsKey(outterFK.Item1) ? tableCache[outterFK.Item1].TableNum :
                this.tableNum;
                var tableTemp = tableCache[outterFK.Item2[0].Table];
                var fkTemp = outterFK.Item2[0];

                build.Append(string.Format(" Join {0} as t{1}", outterFK.Item1.ToLower(), tableNum));
                build.Append(string.Format(" On t{0}.{1} = t{2}.{3}", outterTableNum, fkTemp.From,
                    tableTemp.TableNum, fkTemp.To));

            if (outterFK.Item2.Count() == 2)
            {
                fkTemp = outterFK.Item2[1];
                tableTemp = tableCache[fkTemp.Table];

                build.Append(string.Format(" Join {0} as t{1}", fkTemp.Table, tableTemp.TableNum));
                build.Append(string.Format(" On t{0}.{1} = t{2}.{3}", outterTableNum, fkTemp.From,
                    tableTemp.TableNum, fkTemp.To));
            }
            else throw new NotSupportedException("与数据库架构不同");

            foreach (var keyValue in tableCache)
            {
                if (keyValue.Key == bigType.GetTableName()) continue;
                foreach (var fk in keyValue.Value.Schem.ForeignKeys)
                {
                    if (fk.Value.Table == bigType.GetTableName() || fk.Value.Table == collectionType.GetTableName()) continue;

                    var outertable = tableCache[fk.Key];

                    build.Append(string.Format(" Join {0} as t{1}", outertable.Schem.TableName, outertable.TableNum));
                    build.Append(string.Format(" On t{0}.{1} = t{2}.{3} ", outertable.TableNum, fk.Value.To,
                        keyValue.Value.TableNum, fk.Value.From));
                }
            }

            tableTemp = tableCache[bigType.GetTableName()];
            build.Append(string.Format(" Where t{0}.{1} = @{1}", tableTemp.TableNum, tableTemp.Schem.PrimaryKeyName));
        }

        private void BindCollectionSelect()
        {
            int index = 0;
            foreach (var keyValue in tableCache)
            {
                if (keyValue.Key == bigType.GetTableName() && bigTypeNum <= 1) continue;
                var properties = keyValue.Value.Type.GetProperties();
                foreach (var property in properties)
                {
                    if (Type.GetTypeCode(property.PropertyType) != TypeCode.Object || property.PropertyType == typeof(Guid))
                    {
                        if (index > 0) build.Append(", ");
                        build.Append(string.Format("t{0}.{1}", keyValue.Value.TableNum, property.Name));
                        index++;
                    }
                }
            }
        }

        private void GetFK(Type type)
        {
            if (type == bigType) this.bigTypeNum++;
            if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>)) return;
            if (tableCache.ContainsKey(type.GetTableName())) return;
            var schem = Config.Instance.GetDbSchemInfo().GetSchem(type);
            tableCache[type.GetTableName()] = new Item() { Schem = schem, TableNum = this.tableNum++, Type = type };
            var properties = type.GetProperties();
            foreach (var property in properties)
            {
                var propertyType = property.PropertyType;
                if (Type.GetTypeCode(propertyType) == TypeCode.Object && propertyType != typeof(Guid))
                {
                    GetFK(propertyType);
                }
            }
        }

        private class Item
        {
            public DbSchemItem Schem { get; set; }
            public int TableNum { get; set; }
            public Type Type { get; set; }
        }
    }
}
